/*
 * rentals.c
 *
 *  Created on: Apr 29, 2011
 *      Author: Gilad & Yotam
 */

#include "rentals.h"
#include "set.h"
#include "list.h"
#include "bill.h"
#include "car.h"
#include "customer.h"
#include "mtm_ex2.h"
#include <stdlib.h>
#include <assert.h>

struct Rentals_t {
	Set cars;
	Set customers;
	int carsPayedFor;
	int totalIncome;
	int numberOfBillsClosed;
};

/**=================== start of static functions ===================**/

/**
 * setToList: copies all the elements from a set to a list of the same type
 *@param set - the set to copy from
 *@param copyFunction - the copyFunction for that type
 *@param freeFunction - the freeFunction for that type
 *return:
 *	NULL if memory allocation failed
 *	the newly created list otherwise.
 */
static List setToList(Set set, CopyListElement copyFunction,
											FreeListElement freeFunction) {
	List newList = listCreate(copyFunction, freeFunction);
	if (newList == NULL) {
		return NULL;
	}
	SET_FOREACH(void*, element, set) {
		if (listInsertLast(newList, element) == LIST_OUT_OF_MEMORY) {
			listDestroy(newList);
			return NULL;
		}
	}
	return newList;
}

static void printRentedCarsList(FILE* output, List rentedCarsList) {
	LIST_FOREACH(Car, currentCar, rentedCarsList) {
		mtmPrintCar(output, carGetName(currentCar),
				carGetLicensePlate(currentCar), carGetKm(currentCar),
				carGetPrice(currentCar), carGetSaleStatus(currentCar));
	}
}
static void printBillList(FILE* output, List billList) {
	LIST_FOREACH(BillElement, billIterator, billList) {
		if (billGetCarSaleStatus(billIterator)) {
			mtmPrintBillDiscountLine(output,
					billGetCarName(billIterator),
					billGetCarLicense(billIterator),
					billGetKmDrove(billIterator), billGetPrice(billIterator));
		} else {
			mtmPrintBillLine(output, billGetCarName(billIterator),
					billGetCarLicense(billIterator),
					billGetKmDrove(billIterator),
					billGetPricePerKm(billIterator));
		}
	}
}

/**==================== end of static functions ====================**/

Rentals rentalsCreateNew() {
	Rentals newRentals = malloc(sizeof(struct Rentals_t));
	if (newRentals == NULL) {
		return NULL;
	}
	newRentals->cars = NULL;
	newRentals->customers = NULL;

	Set newCarsSet = setCreate(carCopy, carFree, carCompareByLicensePlate);
	if (newCarsSet == NULL) {
		rentalsDestroy(newRentals);
		return NULL;
	}
	newRentals->cars = newCarsSet;

	Set newCustomersSet = setCreate(copyCustomer, freeCustomer,
			compareCustomerId);
	if (newCustomersSet == NULL) {
		rentalsDestroy(newRentals);
		return NULL;
	}
	newRentals->customers = newCustomersSet;
	newRentals->carsPayedFor = 0;
	newRentals->totalIncome = 0;
	newRentals->numberOfBillsClosed = 0;
	return newRentals;
}

RentalsResult rentalsDestroy(Rentals rentals) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	setDestroy(rentals->cars);
	setDestroy(rentals->customers);
	free(rentals);
	return RENTALS_SUCCESS;
}

RentalsResult rentalsAddNewCar(Rentals rentals, int license, char* name,
		int price, int km) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	if (price <= 0) {
		return RENTALS_INVALID_PRICE;
	}
	if (km < 0) {
		return RENTALS_INVALID_KM;
	}
	Car newCar = carCreate(license, name, price, km);
	if (newCar) {
		SetResult setResult = setAdd(rentals->cars, newCar);
		carFree(newCar);
		switch (setResult) {
		case SET_ITEM_ALREADY_EXISTS:
			return RENTALS_CAR_ALREADY_EXISTS;
		case SET_SUCCESS:
			return RENTALS_SUCCESS;
		default:
			//shouldn't get here
			assert(false);
		}
	}
	return RENTALS_OUT_OF_MEMORY;
}

RentalsResult rentalsRemoveCar(Rentals rentals, int license) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	SET_FOREACH(Car, currentCar, rentals->cars) {
		if (carGetLicensePlate(currentCar) == license) {
			if (!carGetCarInUse(currentCar)) {
				setRemove(rentals->cars, currentCar);
				return RENTALS_SUCCESS;
			} else {
				return RENTALS_CAR_IN_USE;
			}
		}
	}
	return RENTALS_CAR_DOES_NOT_EXISTS;
}

RentalsResult rentalsPutOnSale(Rentals rentals, int license, int minKm,
		int maxKm, int salePrice) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	if ((minKm < 1) || (maxKm < minKm)) {
		return RENTALS_INVALID_KM;
	}
	if (salePrice <= 0) {
		return RENTALS_INVALID_PRICE;
	}
	SET_FOREACH(Car, currentCar, rentals->cars) {
		if (carGetLicensePlate(currentCar) == license) {
			CarResult carResult = carSale(currentCar, minKm, maxKm, salePrice);
			switch (carResult) {
			case CAR_ALREADY_ON_SALE:
				return RENTALS_CAR_ALREADY_ON_SALE;
			case CAR_SALE_PRICE_TOO_BIG:
				return RENTALS_SALE_PRICE_TOO_BIG;
			case CAR_IN_USE:
				return RENTALS_CAR_IN_USE;
			default:
				assert(carResult == CAR_SUCCESS);
				return RENTALS_SUCCESS;
			}
		}
	}
	return RENTALS_CAR_DOES_NOT_EXISTS;
}

RentalsResult rentalsEndSale(Rentals rentals, int license) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	SET_FOREACH(Car, currentCar, rentals->cars) {
		if (carGetLicensePlate(currentCar) == license) {
			CarResult carResult = carNoSale(currentCar);
			switch (carResult) {
			case CAR_IN_USE:
				return RENTALS_CAR_IN_USE;
			case CAR_NO_SALE:
				return RENTALS_CAR_NO_SALE;
			default:
				return RENTALS_SUCCESS;
			}
		}
	}
	return RENTALS_CAR_DOES_NOT_EXISTS;
}

RentalsResult rentalsAddNewCustomer(Rentals rentals, int id, char* name) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	Customer newCustomer = customerCreate(id, name);
	if (newCustomer) {
		SetResult setResult = setAdd(rentals->customers, newCustomer);
		freeCustomer(newCustomer);
		switch (setResult) {
		case SET_ITEM_ALREADY_EXISTS:
			return RENTALS_CUSTOMER_ALREADY_EXISTS;
		case SET_SUCCESS:
			return RENTALS_SUCCESS;
		default:
			//shouldn't get here
			assert(false);
		}
	}
	return RENTALS_OUT_OF_MEMORY;
}

RentalsResult rentalsCustomerRentCar(Rentals rentals, int id, int license) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	SET_FOREACH(Car, currentCar, rentals->cars) {
		if (carGetLicensePlate(currentCar) == license) {
			if (!carGetCarInUse(currentCar)) {
				SET_FOREACH(Customer, currentCustomer, rentals->customers) {
					if (customerGetId(currentCustomer) == id) {
						CustomerResult customerResult = customerRentCar(
								currentCustomer, currentCar);

						if (customerResult == CUSTOMER_OUT_OF_MEMORY) {
							return RENTALS_OUT_OF_MEMORY;
						}
						assert(customerResult == CUSTOMER_SUCCESS);
						carSetUseMode(currentCar, true);
						rentals->carsPayedFor += 1;
						return RENTALS_SUCCESS;
					}
				}
				return RENTALS_INVALID_CUSTOMER_ID;
			} else {
				return RENTALS_CAR_IN_USE;
			}
		}
	}
	return RENTALS_CAR_DOES_NOT_EXISTS;
}

RentalsResult rentalsCustomerReturnCar(Rentals rentals, int id, int license,
		int km) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	SET_FOREACH(Customer, currentCustomer, rentals->customers) {
		if (customerGetId(currentCustomer) == id) {
			if (km > 0) {
				CustomerResult customerResult = customerReturnCar(
						currentCustomer, license, km);
				switch (customerResult) {
				case CUSTOMER_CAR_NOT_RENTED:
					return RENTALS_INVALID_CAR;
				case CUSTOMER_OUT_OF_MEMORY:
					return RENTALS_OUT_OF_MEMORY;
				default:
					assert(customerResult == CUSTOMER_SUCCESS);
					SET_FOREACH(Car, currentCar, rentals->cars) {
						if (carGetLicensePlate(currentCar) == license) {
							carUpdateKm(currentCar, km);
							carSetUseMode(currentCar, false);
							return RENTALS_SUCCESS;
						}
					}
					//if got here, something is wrong with the database.
					assert(false);
				}
			} else {
				return RENTALS_INVALID_KM;
			}
		}
	}
	return RENTALS_INVALID_CUSTOMER_ID;
}

RentalsResult rentalsCustomerPayBill(Rentals rentals, int id) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	SET_FOREACH(Customer, currentCustomer, rentals->customers) {
		if (customerGetId(currentCustomer) == id) {
			int payment = customerGetFee(currentCustomer);
			if (payment) {
				rentals->numberOfBillsClosed += 1;
				rentals->totalIncome += payment;
				customerPayBill(currentCustomer);
				return RENTALS_SUCCESS;
			} else {
				//the customer exists but doesn't have any fees.
				break;
			}
		}
	}
	return RENTALS_INVALID_CUSTOMER_ID;
}

RentalsResult rentalsPrintAvailableCars(Rentals rentals, FILE* output) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	List allCarsList = setToList(rentals->cars, carCopy, carFree);
	List availableCarList = listFilter(allCarsList, carFilterNotInUse);
	listDestroy(allCarsList);
	if (availableCarList == NULL) {
		//if got here then one of the last two memory allocations failed
		return RENTALS_OUT_OF_MEMORY;
	}
	if (listGetSize(availableCarList)) {
		listSort(availableCarList, carCompareByKm);
		listSort(availableCarList, carCompareByName);
		LIST_FOREACH(Car, currentCar, availableCarList) {
			mtmPrintCar(output, carGetName(currentCar), carGetLicensePlate(
					currentCar), carGetKm(currentCar), carGetPrice(currentCar),
					carGetSaleStatus(currentCar));
		}
		listDestroy(availableCarList);
		return RENTALS_SUCCESS;
	}
	listDestroy(availableCarList);
	return RENTALS_NO_CARS;
}

RentalsResult rentalsPrintOpenBills(Rentals rentals, FILE* output) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	List allCustomersList = setToList(rentals->customers, copyCustomer,
			freeCustomer);
	List customersWithFeesList = listFilter(allCustomersList,
			customerFilterOutNoFee);
	listDestroy(allCustomersList);
	if (customersWithFeesList == NULL) {
		//if got here then one of the last two memory allocations failed
		return RENTALS_OUT_OF_MEMORY;
	}

	if (listGetSize(customersWithFeesList)) {
		listSort(customersWithFeesList, compareCustomerFee);
		listSort(customersWithFeesList, compareCustomerId);
		LIST_FOREACH(Customer, currentCustomer, customersWithFeesList) {
			mtmPrintCustomer(output, customerGetName(currentCustomer),
					customerGetId(currentCustomer),
					customerGetNumberOfCarsInBill(currentCustomer));
		}
		listDestroy(customersWithFeesList);
		return RENTALS_SUCCESS;
	}
	listDestroy(customersWithFeesList);
	return RENTALS_NO_OPEN_BILLS;
}

RentalsResult rentalsPrintCustomerBill(Rentals rentals, int id, FILE* output) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	SET_FOREACH(Customer, currentCustomer, rentals->customers) {
		if (customerGetId(currentCustomer) == id) {
			List rentedCarsList = customerGetRentedCarsList(currentCustomer);
			List billList = customerGetBillList(currentCustomer);
			if ((rentedCarsList) && (billList)) {
				mtmPrintBillHeader(output, customerGetName(currentCustomer),
						id);
				printRentedCarsList(output, rentedCarsList);
				printBillList(output, billList);
				mtmPrintBillTotal(output,
						customerGetPastPayment(currentCustomer));
				listDestroy(rentedCarsList);
				listDestroy(billList);
				return RENTALS_SUCCESS;
			} else {
				//one or both memory allocation failed.
				listDestroy(rentedCarsList);
				listDestroy(billList);
				return RENTALS_OUT_OF_MEMORY;
			}
		}
	}
	return RENTALS_INVALID_CUSTOMER_ID;
}

RentalsResult rentalsPrintIncomeSummary(Rentals rentals, FILE* output) {
	if (rentals == NULL) {
		return RENTALS_NULL_ARGUMENT;
	}
	if (rentals->carsPayedFor) {
		int numberOfCarsReturned = rentals->carsPayedFor;
		int totalSales = rentals->totalIncome;
		int averageBill = ((totalSales)/(rentals->numberOfBillsClosed));
		mtmPrintMonthly(output, numberOfCarsReturned, totalSales, averageBill);
		rentals->carsPayedFor = 0;
		rentals->numberOfBillsClosed = 0;
		rentals->totalIncome = 0;
		return RENTALS_SUCCESS;
	}
	return RENTALS_NO_SALES;
}
